// MobileWeather.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "MobileWeather.h"

#include <msxml.h>
#include <objsafe.h>

#include <initguid.h>
#include <imaging.h>

#include <connmgr.h>
#include <wininet.h>

#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))

// [CF] - TODO - explain http://developer.yahoo.com/weather/ documentation
// [CF] - TODO - leave as a chalenge to refer to http://developer.yahoo.com/weather/
//				 documentation and use a menu item that allows toggle between different temperature units

// Executes an XPATH query against an XML document node to select
// a single node and then returns the associated text value.
static void GetString(IXMLDOMNode *pDoc, LPCTSTR pszXPath, LPTSTR pszBuffer, int nMaxLen)
{
	IXMLDOMNode *pNode = NULL;

	BSTR bstrXPath = SysAllocString(pszXPath);

	// Execute the XPATH query
	if (pDoc->selectSingleNode(bstrXPath, &pNode) == S_OK
		&& pNode != NULL)
	{
		BSTR bstrText = NULL;

		// If the expression resulted in a node being
		// selected obtain it's text value.
		if (pNode->get_text(&bstrText) == S_OK
			&& bstrText != NULL)
		{
			// And store it within the buffer the user provided
			// [CF] - TODO - take into account nMaxLen...
			_tcscpy(pszBuffer, bstrText);

			SysFreeString(bstrText);
		}

		pNode->Release();
		pNode = NULL;
	}

	SysFreeString(bstrXPath);
}

// Fetches data from an HTTP or FTP URL and returns it in a
// dynamically allocated buffer. The size of the file that is
// fetched is returned via 'piDataSize'.
static BYTE* FetchURL(LPCTSTR pszUrl, int * piDataSize)
{
	// Allocate an initial 4KB buffer to store the
	// data we fetch
	BYTE* pData = (BYTE *)malloc(4096);
	int iDataMaxSize = 4096;
	
	*piDataSize = 0;

	// Initialise the INET API
	HANDLE hInternet = InternetOpen(_T("MobileWeather"), INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);

	// Create a connection to the desired URL
	HANDLE hFile = InternetOpenUrl(hInternet, pszUrl, NULL, 0, INTERNET_FLAG_NO_UI, NULL);
	if (!hFile)
	{
		DWORD dwError = GetLastError();

		TCHAR szError[MAX_PATH];

		DWORD dwSize = ARRAY_SIZE(szError);
		InternetGetLastResponseInfo(&dwError, szError, &dwSize);
		MessageBox(GetForegroundWindow(), szError, L"Error", MB_OK);
		// [CF] - TODO - deal with this in a better manor...
	}

	// And fetch data from it until we have the
	// complete file transferred
	DWORD dwRead;
	do
	{
		// Double the size of our buffer if it becomes
		// full and we have yet to reach the end
		if (*piDataSize == iDataMaxSize)
		{
			iDataMaxSize *= 2;
			pData = (BYTE *)realloc(pData, iDataMaxSize);
		}

		// Read the next chunk of data available
		// from the URL, appending it to the end
		// of the data already in our buffer.
		dwRead = 0;
		if (!InternetReadFile(hFile, pData + *piDataSize, iDataMaxSize - *piDataSize, &dwRead))
		{
			// An error has occurred (such as the remote server
			// closing our connection early).
			break; // [CF] - TODO - report this error
		}

		// Update the size of the data downloaded
		*piDataSize += dwRead;
	} while (dwRead > 0);
	
	// We've had a read of zero bytes, meaning we've
	// reached the end of the file so close our connection.
	InternetCloseHandle(hFile);

	//InternetCloseHandle(hInternet);

	return pData;
}

/*
static HBITMAP LoadImageFromUrl(LPCTSTR pszUrl)
{
	// Fetch the image data from the website
	int iImageSize = 0;
	BYTE *pImageData = FetchURL(pszUrl, &iImageSize);

	// [CF] - TODO - rework this routine so it asks the IImage for it's
	// dimensions and then creates the bitmap at that size... rather than
	// hardcoding it at 52x52...
	// Then draw the image onto a 52x52 pixel bitmap
	RECT rcBounds = {0, 0, 52, 52};

	HDC hdc = GetDC(NULL);
	HDC memDC = CreateCompatibleDC(hdc);
	HBITMAP hbmp = CreateCompatibleBitmap(hdc, rcBounds.right - rcBounds.left, rcBounds.bottom - rcBounds.top);
	ReleaseDC(NULL, hdc);

	SelectObject(memDC, hbmp);

	// Erase the background of the bitmap
	FillRect(memDC, &rcBounds, (HBRUSH)GetStockObject(WHITE_BRUSH));

	// Create an instance of the ImagingFactory class
	IImagingFactory *pFactory = NULL;
	HRESULT hr = CoCreateInstance(CLSID_ImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IImagingFactory, (void**)&pFactory);
	if (hr == S_OK)
	{
		// Call the CreateImageFromBuffer method to load the image
		// store in the in memory buffer
		IImage *pImage = NULL;
		hr = pFactory->CreateImageFromBuffer(pImageData, iImageSize, BufferDisposalFlagNone, &pImage);
		if (hr == S_OK)
		{
			// And finally draw the image into the device context
			// associated with the bitmap
			hr = pImage->Draw(memDC, &rcBounds, NULL);

			pImage->Release();
			pImage = NULL;
		}

		pFactory->Release();
		pFactory = NULL;
	}

	if (hr != S_OK)
	{
		// [CF] - TODO - remove hbmp from DC...
		DeleteObject(hbmp);
		hbmp = NULL;
	}

	if (pImageData)
	{
		free(pImageData);
		pImageData = NULL;
	}

	DeleteDC(memDC);

	return hbmp;
}

*/

static HBITMAP LoadImageFromUrl(LPCTSTR pszUrl)
{
	HBITMAP hbmp = NULL;

	// Fetch the image data from the website
	int iImageSize = 0;
	BYTE *pImageData = FetchURL(pszUrl, &iImageSize);

	// Create an instance of the ImagingFactory class
	IImagingFactory *pFactory = NULL;
	HRESULT hr = CoCreateInstance(CLSID_ImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IImagingFactory, (void**)&pFactory);
	if (hr == S_OK)
	{
		// Call the CreateImageFromBuffer method to load the image
		// store in the in memory buffer
		IImage *pImage = NULL;
		hr = pFactory->CreateImageFromBuffer(pImageData, iImageSize, BufferDisposalFlagNone, &pImage);
		if (hr == S_OK)
		{
			// Determine how large the image is
			ImageInfo imageInfo;
			memset(&imageInfo, 0, sizeof(imageInfo));
			hr = pImage->GetImageInfo(&imageInfo);
			if (hr == S_OK)
			{
				// Create a bitmap of the same size and
				// associate it with a device context so
				// that we can draw onto it
				RECT rcBounds = {0, 0, imageInfo.Width, imageInfo.Height};

				HDC hdc = GetDC(NULL);
				HDC memDC = CreateCompatibleDC(hdc);
				hbmp = CreateCompatibleBitmap(hdc, imageInfo.Width, imageInfo.Height);
				ReleaseDC(NULL, hdc);

				SelectObject(memDC, hbmp);

				// Erase the background of the bitmap
				FillRect(memDC, &rcBounds, (HBRUSH)GetStockObject(WHITE_BRUSH));

				// And finally draw the image onto the bitmap 
				hr = pImage->Draw(memDC, &rcBounds, NULL);

				pImage->Release();
				pImage = NULL;

				DeleteDC(memDC);
			}
		}

		pFactory->Release();
		pFactory = NULL;
	}

	if (hr != S_OK)
	{
		// [CF] - TODO - remove hbmp from DC...
		DeleteObject(hbmp);
		hbmp = NULL;
	}

	if (pImageData)
	{
		free(pImageData);
		pImageData = NULL;
	}

	return hbmp;
}


// Checks if a valid network connection is present that
// will enable us to access the url 'pszUrl'. If one is
// not present it will attempt to establish one.
static HANDLE CreateNetworkConnection(LPCTSTR pszUrl)
{
	CONNMGR_CONNECTIONINFO ci = {0};
	HANDLE hConnection = NULL;
	DWORD dwStatus = 0;
	HRESULT hr; 

	// Register with the connection manager
	ci.cbSize = sizeof(CONNMGR_CONNECTIONINFO); 
	ci.dwParams = CONNMGR_PARAM_GUIDDESTNET; 
	ci.dwFlags = CONNMGR_FLAG_PROXY_HTTP; 
	ci.dwPriority = CONNMGR_PRIORITY_USERINTERACTIVE; 

	// Ask connection manager which network would best
	// enable us to connect to the URL we aim to use.
	hr = ConnMgrMapURL(pszUrl, &ci.guidDestNet, &dwStatus);
	if (hr != S_OK)
		return NULL;

	// Then request connection manager to establish a
	// connection to that network.
	hr = ConnMgrEstablishConnectionSync(&ci, &hConnection, 25000, &dwStatus); 
	if (hr != S_OK)
		return NULL;

	// [CF] - TODO - Make sure dwStatus == CONNMGR_STATUS_CONNECTED

	return hConnection;
}

static void CloseNetworkConnection(HANDLE hConnection)
{
   if (hConnection)
   {
	   // Tell connection manager we don't need access to the
	   // network anymore. Notice that this may not immediatly
	   // disconnect the network connection, as connection manager
	   // may have other clients that concurrently desire access,
	   // or it may decide to "cache" the connection in case
	   // another application wants to access the same netowrk in
	   // the near future.
       ConnMgrReleaseConnection(hConnection, TRUE);
   }
}

static void CallWebService(HWND hDlg)
{
	TCHAR szUrl[MAX_PATH];
	TCHAR szZIPCode[MAX_PATH];

	// Determine the URL needed to get the weather forecast
	GetDlgItemText(hDlg, IDC_EDIT_ZIPCODE, szZIPCode, ARRAY_SIZE(szZIPCode));
	_stprintf(szUrl, _T("http://weather.yahooapis.com/forecastrss?p=%s"), szZIPCode);

	// Ensure we have a suitable connection to the network
	HANDLE hConnection = CreateNetworkConnection(szUrl);
	if (!hConnection)
	{
		MessageBox(GetForegroundWindow(), _T("Failed to connect to the internet"), _T("Error"), MB_ICONERROR | MB_OK);
		return;
	}

	// [CF] - TODO - catch all errors past this point and display a nice error message if we fail
	// to download/parse the RSS document...
	
	// Create an XML Document instance
   	IXMLDOMDocument2 *pDoc = NULL;
	HRESULT hr = CoCreateInstance(CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER, IID_IXMLDOMDocument2, (void**)&pDoc);
	if (hr != S_OK)
		return;

	// Tell MSXML we want to use XPATH as the query language and
	// specify the details of the yweather namespace.
	VARIANT sss;

	// [CF] - TODO - tidy up this section of the code
	VariantInit(&sss);
	V_BSTR(&sss) = SysAllocString(L"XPath");
	V_VT(&sss) = VT_BSTR;
	hr = pDoc->setProperty(SysAllocString(L"SelectionLanguage"), sss);

	VariantInit(&sss);
	V_BSTR(&sss) = SysAllocString(L"xmlns:yweather=\"http://xml.weather.yahoo.com/ns/rss/1.0\""); 
	V_VT(&sss) = VT_BSTR;
	hr = pDoc->setProperty(SysAllocString(L"SelectionNamespaces"), sss);

	// Load the yahoo weather forecast RSS feed into the DOM Document.
	// MSXML is able to fetch data from HTTP URLs directly.
	VARIANT_BOOL bSuccess = VARIANT_FALSE;
	VARIANT filename;

	VariantInit(&filename);
	V_BSTR(&filename) = SysAllocString(szUrl);
	V_VT(&filename) = VT_BSTR;

	pDoc->put_async(VARIANT_FALSE);

	hr = pDoc->load(filename, &bSuccess);
	if (hr == S_OK && bSuccess == VARIANT_TRUE)
	{
		// [CF] - TODO - don't make these static and give sensible lengths
		static TCHAR szTitle[5 * MAX_PATH];

		static TCHAR szConditionText[5 * MAX_PATH];
		static TCHAR szConditionTemp[5 * MAX_PATH];
		static TCHAR szConditionCode[5 * MAX_PATH];
		static TCHAR szTemperatureUnits[5 * MAX_PATH];
		
		static TCHAR szForecastDate[5 * MAX_PATH];
		static TCHAR szForecastLow [5 * MAX_PATH];
		static TCHAR szForecastHigh[5 * MAX_PATH];
		static TCHAR szForecastText[5 * MAX_PATH];

		// Extract the information from the XML document
		// [CF] - TODO - should i strip off the "Conditions for " prefix in szTitle?
		GetString(pDoc, L"/rss/channel/item/title/text()", szTitle, ARRAY_SIZE(szTitle));

		GetString(pDoc, L"/rss/channel/item/yweather:condition/@text", szConditionText, ARRAY_SIZE(szConditionText));
		GetString(pDoc, L"/rss/channel/item/yweather:condition/@temp", szConditionTemp, ARRAY_SIZE(szConditionTemp));
		GetString(pDoc, L"/rss/channel/item/yweather:condition/@code", szConditionCode, ARRAY_SIZE(szConditionCode));

		GetString(pDoc, L"/rss/channel/item/yweather:forecast[last()]/@date", szForecastDate, ARRAY_SIZE(szForecastDate));
		GetString(pDoc, L"/rss/channel/item/yweather:forecast[last()]/@low", szForecastLow, ARRAY_SIZE(szForecastLow));
		GetString(pDoc, L"/rss/channel/item/yweather:forecast[last()]/@high", szForecastHigh, ARRAY_SIZE(szForecastHigh));
		GetString(pDoc, L"/rss/channel/item/yweather:forecast[last()]/@text", szForecastText, ARRAY_SIZE(szForecastText));

		GetString(pDoc, L"/rss/channel/yweather:units/@temperature", szTemperatureUnits, ARRAY_SIZE(szTemperatureUnits));

		// Update the UI based upon that information obtained above
		SetDlgItemText(hDlg, IDC_LBL_TITLE, szTitle);

		static TCHAR szTemp[MAX_PATH];
		_stprintf(szTemp, _T("%s - %s %s"), szConditionText, szConditionTemp, szTemperatureUnits);
		SetDlgItemText(hDlg, IDC_LBL_CURRENTCONDITIONSVALUE, szTemp);

		_stprintf(szTemp, _T("%s - %s.\r\nHigh: %s Low: %s"), szForecastDate, szForecastText, szForecastHigh, szForecastLow);
		SetDlgItemText(hDlg, IDC_LBL_FORECASTVALUE, szTemp);

		_stprintf(szTemp, _T("http://l.yimg.com/a/i/us/we/52/%s.gif"), szConditionCode);
		HBITMAP hbmpConditionCode = LoadImageFromUrl(szTemp);
		SendDlgItemMessage(hDlg, IDC_WEATHER_IMAGE, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hbmpConditionCode);
	}

	pDoc->Release();
	pDoc = NULL;

	CloseNetworkConnection(hConnection);
}

// Message handler for Mobile Weather dialog
INT_PTR CALLBACK MobileWeatherDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_INITDIALOG:
            {
                // Create a Done button and size it.  
                SHINITDLGINFO shidi;
				SHMENUBARINFO mbi;
                shidi.dwMask = SHIDIM_FLAGS;
                shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIPDOWN | SHIDIF_SIZEDLGFULLSCREEN | SHIDIF_EMPTYMENU;
                shidi.hDlg = hDlg;
                SHInitDialog(&shidi);

				// Create a menu bar
                memset(&mbi, 0, sizeof(SHMENUBARINFO));
				mbi.cbSize     = sizeof(SHMENUBARINFO);
				mbi.hwndParent = hDlg;
				mbi.nToolBarId = IDR_MENU;
				mbi.hInstRes   = GetModuleHandle(NULL);
				mbi.dwFlags = SHCMBF_HMENU;
	            SHCreateMenuBar(&mbi);

				// Change some of the labels to use a bold font
				LOGFONT lf;
				memset(&lf, 0, sizeof(LOGFONT));
				HDC hdc = GetDC(NULL);
				lf.lfHeight = -9 * GetDeviceCaps(hdc, LOGPIXELSY) / 72;
				ReleaseDC(NULL, hdc);
				lf.lfWeight = FW_BOLD;
				HFONT hFont = CreateFontIndirect(&lf);

				SendDlgItemMessage(hDlg, IDC_LBL_TITLE, WM_SETFONT, (WPARAM)hFont, 0);
				SendDlgItemMessage(hDlg, IDC_LBL_ZIPCODE, WM_SETFONT, (WPARAM)hFont, 0);
				SendDlgItemMessage(hDlg, IDC_LBL_CURRENTCONDITIONS, WM_SETFONT, (WPARAM)hFont, 0);
				SendDlgItemMessage(hDlg, IDC_LBL_FORECAST, WM_SETFONT, (WPARAM)hFont, 0);

				SetDlgItemText(hDlg, IDC_EDIT_ZIPCODE, _T("98052"));

				// Kick off a query to the webservice
				PostMessage(hDlg, WM_COMMAND, IDC_REFRESH, 0);
            }
            return (INT_PTR)TRUE;

		// [CF] - TODO - add divider line within WM_PAINT...

        case WM_COMMAND:
			switch (LOWORD(wParam))
			{
			case IDC_REFRESH:
				{
				HCURSOR hCursor = SetCursor(LoadCursor(GetModuleHandle(NULL), IDC_WAIT));
				CallWebService(hDlg);
				SetCursor(hCursor);
				}
				break;

			case IDOK:
                EndDialog(hDlg, LOWORD(wParam));
                return (INT_PTR)TRUE;
            }
            break;

        case WM_CLOSE:
            EndDialog(hDlg, message);
            return (INT_PTR)TRUE;

    }
    return (INT_PTR)FALSE;
}

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPTSTR    lpCmdLine,
                   int       nCmdShow)
{
	CoInitializeEx(NULL, COINIT_MULTITHREADED);

	return DialogBox(hInstance, (LPCTSTR)IDD_MOBILEWEATHER, NULL, MobileWeatherDialog);
}